
use std::sync::Arc;
use std::{borrow::Cow, ptr};
use std::io::{self, Write};
use tokio::task::{spawn_blocking};
use winapi::um::fileapi::{FlushFileBuffers, WriteFile};
use winapi::{shared::{minwindef::{DWORD, LPVOID}, ntdef::HANDLE}, um::{commapi::{GetCommState, SetCommState, SetCommTimeouts}, fileapi::{CreateFileW, ReadFile, OPEN_EXISTING}, handleapi::{CloseHandle, INVALID_HANDLE_VALUE}, winbase::{CBR_115200, COMMTIMEOUTS, DCB, MARKPARITY, ONESTOPBIT}, winnt::{FILE_ATTRIBUTE_NORMAL, GENERIC_READ, GENERIC_WRITE, MAXDWORD}}};

use crate::error::{AppErr, ErrWrap};
use crate::utils::UnSafeAsMut;


unsafe impl Send for WinPort {}
unsafe impl Sync for WinPort {}

pub struct WinPort {
    handle: HANDLE,
}

pub type Port = Arc<WinPort>;

fn get_dcb(handle: HANDLE) -> Result<DCB, AppErr> {

    unsafe {
        let mut dcb: DCB = std::mem::zeroed();
        dcb.DCBlength = std::mem::size_of::<DCB>() as DWORD;
        if GetCommState(handle, &mut dcb) == 0 {
            return Err(AppErr::Custom(Cow::Borrowed("get dcb fail")));
        }
        Ok(dcb)
    }
}

impl Drop for WinPort {

    fn drop(&mut self) {
        unsafe {
            CloseHandle(self.handle);
        }
    }
}

impl io::Read for WinPort {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let mut len: DWORD = 0;

        match unsafe {
            ReadFile(
                self.handle,
                buf.as_mut_ptr() as LPVOID,
                buf.len() as DWORD,
                &mut len,
                ptr::null_mut(),
            )
        } {
            0 => Err(io::Error::last_os_error()),
            _ => {
                if len != 0 {
                    Ok(len as usize)
                } else {
                    Err(io::Error::new(
                        io::ErrorKind::TimedOut,
                        "Operation timed out",
                    ))
                }
            }
        }
    }
}

impl io::Write for WinPort {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let mut len: DWORD = 0;

        match unsafe {
            WriteFile(
                self.handle,
                buf.as_ptr() as LPVOID,
                buf.len() as DWORD,
                &mut len,
                ptr::null_mut(),
            )
        } {
            0 => Err(io::Error::last_os_error()),
            _ => Ok(len as usize),
        }
    }

    fn flush(&mut self) -> io::Result<()> {
        match unsafe { FlushFileBuffers(self.handle) } {
            0 => Err(io::Error::last_os_error()),
            _ => Ok(()),
        }
    }
}

impl WinPort {

    pub fn open(name: &str) -> Result<Self, AppErr> {

        let mut name16 = Vec::<u16>::with_capacity(4 + name.len() + 1);

        name16.extend(r"\\.\".encode_utf16());
        name16.extend(name.encode_utf16());
        name16.push(0);

        let handle = unsafe {
            CreateFileW(
                name16.as_ptr(),
                GENERIC_READ | GENERIC_WRITE,
                0,
                ptr::null_mut(),
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                0 as HANDLE,
            )
        };
        if handle == INVALID_HANDLE_VALUE {
            return Err(AppErr::Custom(Cow::Borrowed("打开失败")));
        }
        let port = WinPort {
            handle
        };

        let mut dcb = get_dcb(handle)?;
        dcb.BaudRate = CBR_115200;
        dcb.ByteSize = 8;
        dcb.StopBits = ONESTOPBIT;
        dcb.Parity = MARKPARITY;
        dcb.set_fParity(1);

        unsafe {
            if SetCommState(handle, &mut dcb) == 0 {
                return Err(AppErr::Custom(Cow::Borrowed("set dcb fail")));
            }
        }
        let mut options = COMMTIMEOUTS {
            ReadIntervalTimeout: 10,
            ReadTotalTimeoutConstant: 100,
            ReadTotalTimeoutMultiplier: 10,
            WriteTotalTimeoutConstant: MAXDWORD,
            WriteTotalTimeoutMultiplier: MAXDWORD,
        };
        unsafe {
            if SetCommTimeouts(handle, &mut options) == 0 {
                return Err(AppErr::Custom(Cow::Borrowed("set timeout fail")));
            }
        }

        Ok(port)
    }

    pub async fn open_async(name: String) -> Result<Port, AppErr> {
        let p = spawn_blocking(move|| {
            return Self::open(&name)
        }).await.wrap()??;
        Ok( Arc::new(p) )
    }
}

pub async fn write(p: Port, buf: Box<[u8]>) -> Result<(), AppErr> {

    spawn_blocking(move || {
        let port = &*p;
        port.to_mut().write_all(&buf)
    }).await.wrap()??;
    Ok(())
}


